Session Handoff — v1 (Review, read-only) One controller at a time. WAITING → IN_PROGRESS → RETURNED → COMPLETED. Terminal: DECLINED / CANCELLED / EXPIRED / RECALLED.
Status colour law — waiting = neutral · in-progress = terracotta · returned/completed = green · recalled/expired = amber · declined/cancelled = grey · danger red is reserved for Recall & Revoke.
Frame 1 · Initiate — session action menu390×844 · dark · placement only, one new row
9:41
Panda · MacBook Pro · ~/proj/app/cc-pocket
YOU
where does the SSE stream get assembled?
CLAUDE
It's in StreamParser.feed() in the protocol module — partial frames buffer there until a blank line terminates the event.
YOU
add a unit test for the stream parser
Bashgradle :protocol:test42 passed
CLAUDE
Suite is green. The parser now emits exactly one event when a frame is split across chunks.
Message Claude…
Anchor: in the chat header (44×44). Popover 256px, r14, raised + hairline.
The new row is the only one with a leading terracotta glyph; everything else
stays neutral. Scrim starts under the header so the session you're handing
off stays legible. NEW badge is temporary (2 releases).
Frame 2 · Handoff draft — full sheet390×1420 scrolling sheet · dark · exceeds the 844 viewport — scroll region in Compose
hero = trust boundary card

Hand off this session

Refactor auth moduleClaude
~/proj/app/cc-pocket·feat/auth-refactor
Rolecontinue arrives later
Reviewread-only
Continuecan edit · later
Recipient
@FrankAdd a name or label…
Invite expires
1h4h24h
THEY WILL SEE
  • The full session transcriptevery message, tool call and diff
  • Code in the shared roots~/proj/app/cc-pocket
    ~/proj/app/cc-pocket-protocol
  • Read-only tools & diffsRead · Grep · Glob · git diff
THEY CAN'T DO
  • Edit or write files
  • Reach your other sessions or folders
  • Change device settings or credentials
  • Pass this handoff on to someone else
The full conversation history of this session will be visible — including anything you pasted into it.
Handoff briefautoEdit
Goal

Split AuthInterceptor so token refresh is testable without a live socket.

Done

Extracted TokenStore, moved refresh into RefreshCoordinator, 42 tests green.

Current state

Working tree clean on feat/auth-refactor, 6 files changed.

Ask

Review the concurrency around the refresh mutex before I open the PR.

Focus areas
  • RefreshCoordinator.kt
  • TokenStore.kt — persistence path
The boundary card is the hero — it sits above the fold-adjacent brief and
uses the same two-list grammar as folder share. Mobile stacks the two
columns; the honest strip spans full width on a raised background.
Role is a two-up card segment, not a pill row, so the read-only sub-label
always reads. Continue is present-but-disabled to set the roadmap.
The @Frank remove glyph draws at 18px but takes a 44×44 hit slop.
Frame 3a · Invite ready — share sheet390×600 · dark · QR + short code

Invite ready

WAITING
H7QX-2MRD
expires in 23:59:12
Frank·Review·read-only·24h
Short code is the fallback for anyone who can't scan: 8 chars, mono, 27px,
0.1em tracking, ambiguous glyphs (0/O, 1/I) excluded from the alphabet.
Countdown is caption-weight — informational, never alarming, until <5 min
when it turns amber.
Frame 3b · Source chat — WAITING lock390×844 · dark · composer disabled, stream still scrollable
9:41
Panda · MacBook Pro · ~/proj/app/cc-pocket
YOU
add a unit test for the stream parser
Bashgradle :protocol:test42 passed
CLAUDE
Suite is green — the parser emits exactly one event when a frame straddles a chunk boundary. Working tree is clean on feat/auth-refactor.
Handoff created for Frank · Review · expires in 24h
Waiting for Frank to accept
Review · read-only · H7QX-2MRD · 23:57:04 left
Input locked during handoff
Waiting is neutral, not terracotta — nothing is running yet. The banner is a
pinned card above the composer (not a toast) because the lock persists.
Composer is dimmed to 45% and shows a lock glyph — it is visibly present,
not removed, so the return to normal is obvious. Recall is the only
danger-tinted control on the screen. Both banner buttons are 44pt tall.
Frame 4 · Recipient — accept preview390×1312 scrolling · dark · exceeds the 844 viewport — scroll region in Compose
the trust screen; footer buttons pinned
9:41
PPanda · MacBook Pro
Refactor auth module
~/proj/app/cc-pocket·feat/auth-refactor
Code reviewroleReview
Read-onlyaccess levelno writes
Returns in 4hor when you finish
Runs on Panda's computerand Panda's model account — you're not billed
YOU WILL SEE
  • Panda's full session transcriptevery message, tool call and diff
  • Code in the shared roots~/proj/app/cc-pocket
    ~/proj/app/cc-pocket-protocol
  • Read-only tools & diffsRead · Grep · Glob · git diff
YOU CAN'T DO
  • Edit, write or commit files
  • Open Panda's other sessions or folders
  • Touch device settings or credentials
  • Hand this session on to anyone else
Panda can recall control at any time. Your messages are recorded in their session.
Handoff brief
Goal

Split AuthInterceptor so token refresh is testable without a live socket.

Ask

Review the concurrency around the refresh mutex before the PR opens.

Focus areas
  • RefreshCoordinator.kt
  • TokenStore.kt — persistence path
Mirror of Frame 2, second person. Facts first (who / what / where), then the
same boundary grammar, then the brief. The cost note is a row, not fine
print — “runs on Panda's computer and model account” is the single
question every recipient asks.
Frame 4b · Recipient — expired invite390×560 · dark · inline error variant
9:41
This invite has expired
H7QX-2MRD · expired 12 min ago
Ask Panda to send a new one.
PPanda · MacBook Pro
~/proj/app/cc-pocket·feat/auth-refactor
Code reviewrole
Read-onlyaccess level
Expired keeps the content visible at 32% so the recipient can still tell
what they missed — but every action collapses to a single Close.
Same shell serves DECLINED, CANCELLED and RECALLED with a swapped
banner string and chip.
Frame 5 · Recipient trust screen — light390×1312 scrolling · light · token-for-token lock of Frame 4
9:41
PPanda · MacBook Pro
Refactor auth module
~/proj/app/cc-pocket·feat/auth-refactor
Code reviewroleReview
Read-onlyaccess levelno writes
Returns in 4hor when you finish
Runs on Panda's computerand Panda's model account — you're not billed
YOU WILL SEE
  • Panda's full session transcriptevery message, tool call and diff
  • Code in the shared roots~/proj/app/cc-pocket
    ~/proj/app/cc-pocket-protocol
  • Read-only tools & diffsRead · Grep · Glob · git diff
YOU CAN'T DO
  • Edit, write or commit files
  • Open Panda's other sessions or folders
  • Touch device settings or credentials
  • Hand this session on to anyone else
Panda can recall control at any time. Your messages are recorded in their session.
Handoff brief
Goal

Split AuthInterceptor so token refresh is testable without a live socket.

Ask

Review the concurrency around the refresh mutex before the PR opens.

Focus areas
  • RefreshCoordinator.kt
  • TokenStore.kt — persistence path
Light swaps tokens only — no layout change. Accent darkens to #C75A38
for AA on warm white; primary button text goes white. Success/muted
marks keep their roles so the two lists stay distinguishable without
relying on colour alone (glyph shape carries it).
Frame 6 · Recipient in control — IN_PROGRESS390×844 · dark · terracotta ribbon, actor attribution
9:41
PFContinuing Panda's session · Review3:41:22
Panda · MacBook Pro · ~/proj/app/cc-pocketread-only tools
You joined this session for code review — see the Handoff brief
PANDA
add a unit test for the stream parser
Bashgradle :protocol:test42 passed
FRANK (YOU)
show me every call site that touches the refresh mutex
GreprefreshMutex --glob *.kt7 hits
CLAUDE
Seven call sites. Six take the mutex before reading TokenStore; RefreshCoordinator.kt:88 reads it outside the lock
where else is the store read?
Terracotta means in progress — the ribbon is the only tinted surface.
Attribution appears only once a session has had more than one actor;
solo sessions keep the plain YOU / CLAUDE labels. The recipient's own
label carries a 5px accent tick. Tool rows are untouched — the actor of a
tool call is always the agent.
Frame 7 · Initiator spectating390×844 · dark · neutral ribbon, input locked
9:41
FFrank is continuing this session · Review3:41:22
Panda · MacBook Pro · ~/proj/app/cc-pocket
YOU
add a unit test for the stream parser
Bashgradle :protocol:test42 passed
Frank accepted · took control 18 min ago
FRANK
show me every call site that touches the refresh mutex
GreprefreshMutex --glob *.kt7 hits
CLAUDE
Seven call sites. Six take the mutex before reading TokenStore; RefreshCoordinator.kt:88 reads it outside the lock
Watching — input locked
Same stream, different chrome. The initiator's ribbon is neutral — they are
not the actor, so no terracotta. The composer is replaced here (not dimmed):
during IN_PROGRESS there is no draft to preserve, and the bar carries the
one escape hatch. Streaming output keeps rendering live.
Frame 8 · Return flow — result draft390×900 · dark · structured, editable

Return session to Panda

Panda gets control back and this result is added to the session.

Return resultdraft · autoEdit
Verdict
Approve with fixesApproveRequest changes
Findings · 3
Token store read outside the refresh mutexRefreshCoordinator.kt:88
Retry backoff resets on every 401, so it never growsRefreshCoordinator.kt:142
Test covers the split-chunk case but not an empty frameStreamParserTest.kt:61
Verified
gradle :protocol:test42 passed
gradle :app:lintclean
gradle :app:test1 failed
Suggested next steps
  • Move the TokenStore read inside withLock.
  • Make backoff state survive a 401 burst.
  • Add an empty-frame case to the parser test.
The result is structured, not prose — verdict + findings + verification +
next steps — so it can be rendered as a card, injected into the session as
context, and later diffed. Severity is a 7px dot: red / amber / secondary.
Every finding carries a mono file:line. Verdict chips use the status
palette (amber = attention, not failure).
Frame 9 · Initiator — RETURNED, result in stream390×844 · dark · composer unlocked
9:41
Panda · MacBook Pro · ~/proj/app/cc-pocket
FRANK
show me every call site that touches the refresh mutex
FFrank returned this sessionApprove with fixes
Token store read outside the refresh mutexRefreshCoordinator.kt:88
Retry backoff resets on every 401RefreshCoordinator.kt:142
View all 5 findings →
6 files reviewed · 0 changed
protocol:test 42 ✓ · app:lint clean · app:test 1 ✗
18 min
Result injected into the session for Claude to continue.
Message Claude…
RETURNED is green — the loop closed successfully; the verdict chip carries
its own (amber) severity independently. The card is an in-stream artefact,
not a modal, so it stays scrollable in history. Mark reviewed is the only
transition to COMPLETED. The system row is deliberately plain: the user
should know the result became context.
Frame 10 · Session info — Handoffs390×620 · dark · status chip legend in situ
9:41
Handoffs6
FFrankReview18 min · 5 findingsIn progress
MMeiReviewyesterday · approvedCompleted
FFrankReview2 days ago · pulled back after 4 minRecalled
AAikoReview3 days agoDeclined
MMeiReviewlast week · never openedExpired
?Unsent inviteReviewlast weekCancelled
Frame 10b · Handoffs — empty390×300 · dark
Handoffs

Hand off this session to get a colleague's eyes on it.

Rows are 60px, whole row tappable, chevron trailing. The status chip is
the row's only colour. In progress pulses (1.4s, opacity 1→.45) — it is
the one live state. Expired and Recalled share amber because both mean
“ended without a result”; Declined / Cancelled are grey — a non-event.
Desktop — 1200pt two-paneLeft session sidebar 288px · right chat pane. Same tokens, same status law, denser rhythm.
Frame 11 · Desktop — WAITING + draft modal1200×800 · dark · two-column dialog
Panda · MacBook Pro
Refactor auth module~/proj/app/cc-pocket · waiting for Frank
Fix flaky socket test~/proj/app/cc-pocket-protocol
Release notes 1.6~/proj/app/cc-pocket
Gateway model presets~/proj/app/gateway
Refactor auth moduleWAITINGfeat/auth-refactordefault
YOU
add a unit test for the stream parser
Bashgradle :protocol:test42 passed
CLAUDE
Suite is green — the parser emits exactly one event when a frame straddles a chunk boundary. Working tree is clean on feat/auth-refactor.
Handoff created for Frank · Review · expires in 24h
Waiting for Frank to accept
Review · read-only · H7QX-2MRD · 23:57:04 left
Input locked during handoff

Hand off this session

Refactor auth moduleClaude
~/proj/app/cc-pocket·feat/auth-refactor
Rolecontinue arrives later
Reviewread-only
Continuecan edit · later
Recipient
@FrankAdd a name or label…
Invite expires
1h4h24h
THEY WILL SEE
  • The full session transcriptevery message, tool call and diff
  • Code in the shared roots~/proj/app/cc-pocket
    ~/proj/app/cc-pocket-protocol
  • Read-only tools & diffsRead · Grep · Glob · git diff
THEY CAN'T DO
  • Edit or write files
  • Reach your other sessions or folders
  • Change device settings or credentials
  • Pass this handoff on to someone else
The full conversation history of this session will be visible — including anything you pasted into it.
Handoff briefautoEdit
Goal

Split AuthInterceptor so token refresh is testable without a live socket.

Ask

Review the concurrency around the refresh mutex before I open the PR.

Your input locks until Frank returns
Desktop keeps the mobile information order but splits it across two columns: decisions left,
consequences right. The boundary card goes back to its native two-up form. The dialog footer
restates the cost of the action next to the primary button — desktop users click faster.
Sidebar: the active session row gains a status dot (neutral = waiting) and a lock glyph;
other rows are unaffected, so a handoff never disturbs the rest of the fleet.
Frame 12 · Desktop — recipient in control + result1200×800 · dark · terracotta ribbon, two-column findings
Panda · MacBook Pro guest
Refactor auth module~/proj/app/cc-pocket · you have control
Not shared with youPanda's other sessions stay hidden
Refactor auth moduleIN PROGRESSfeat/auth-refactorread-only tools
PFContinuing Panda's session · Reviewreturns in 3:41:22
PANDA
add a unit test for the stream parser
FRANK (YOU)
show me every call site that touches the refresh mutex
GreprefreshMutex --glob *.kt7 hits
FFrank returned this sessionApprove with fixes18 min
Findings · 5
Token store read outside the refresh mutexRefreshCoordinator.kt:88
Retry backoff resets on every 401RefreshCoordinator.kt:142
Refresh failure swallows the causeAuthInterceptor.kt:57
Empty-frame case missing from the parser testStreamParserTest.kt:61
Persistence path is hard-codedTokenStore.kt:24
Two unused imports after the splitAuthInterceptor.kt:1
6 files reviewed · 0 changed
protocol:test 42 ✓
app:lint clean
app:test 1 ✗
where else is the store read?
The guest's sidebar shows exactly one session plus an explicit “not shared with you” row —
absence is stated, never implied. Finish & return is a pane-header button on desktop
(persistent, top-right) instead of the mobile pill above the composer.
The result card grows to 820px and findings go two-up; the card is capped so long results
scroll inside the stream rather than stretching the pane.